import ctypes
from ctypes import *
import os
import platform
from datetime import datetime
from ctypes import memset, memmove  # 添加必要的函数导入

# 加载海康SDK库
def load_sdk():
    system = platform.system()
    sdk_libs = {
        "Windows": "HCNetSDK.dll",
        "Linux": "libhcnetsdk.so"
    }
    
    if system not in sdk_libs:
        raise Exception(f"不支持的操作系统: {system}")
    
    # 获取项目lib文件夹路径
    current_dir = os.path.dirname(os.path.abspath(__file__))
    lib_dir = os.path.join(current_dir, "lib")
    sdk_path = os.path.join(lib_dir, sdk_libs[system])
    
    # 检查库文件是否存在
    if not os.path.exists(sdk_path):
        raise FileNotFoundError(f"""SDK库文件不存在: {sdk_path}
        请确认已将SDK库文件放置在项目的lib文件夹中。
        """)
    
    # 尝试加载库
    try:
        sdk = cdll.LoadLibrary(sdk_path)
        return sdk
    except OSError as e:
        error_msg = f"加载SDK失败: {e}"
        raise Exception(error_msg)

# 定义SDK所需结构体
class NET_DVR_TIME(Structure):
    _fields_ = [
        ("dwYear", c_uint),
        ("dwMonth", c_uint),
        ("dwDay", c_uint),
        ("dwHour", c_uint),
        ("dwMinute", c_uint),
        ("dwSecond", c_uint)
    ]

class NET_DVR_PLAYCOND(Structure):
    _fields_ = [
        ("dwChannel", c_uint),
        ("struStartTime", NET_DVR_TIME),
        ("struStopTime", NET_DVR_TIME),
        ("byStreamType", c_byte),  # 0-主码流, 1-子码流
        ("byLinkMode", c_byte),    # 0-TCP, 1-UDP, 2-多播
        ("byRes", c_byte * 2)
    ]

class NET_DVR_USER_LOGIN_INFO(Structure):
    _fields_ = [
        ("sDeviceAddress", c_byte * 129),  # 设备地址
        ("wPort", c_uint16),                # 端口号
        ("sUserName", c_byte * 64),         # 用户名
        ("sPassword", c_byte * 64),         # 密码
        ("bUseAsynLogin", c_bool),          # 是否异步登录
        ("byRes", c_byte * 32)              # 保留字节
    ]

class NET_DVR_DEVICEINFO_V40(Structure):
    _fields_ = [
        ("sSerialNumber", c_byte * 48),
        ("byAlarmInPortNum", c_byte),
        ("byAlarmOutPortNum", c_byte),
        ("byDiskNum", c_byte),
        ("byDVRType", c_byte),
        ("byChanNum", c_byte),
        ("byStartChan", c_byte),
        ("byAudioChanNum", c_byte),
        ("byIPChanNum", c_uint16),
        ("byZeroChanNum", c_byte),
        ("byMainProto", c_byte),
        ("bySubProto", c_byte),
        ("bySupport", c_byte),
        ("bySupport1", c_byte),
        ("bySupport2", c_byte),
        ("wDevType", c_uint16),
        ("bySupport3", c_byte),
        ("byMultiStreamProto", c_byte),
        ("bySupport4", c_byte),
        ("bySupport5", c_byte),
        ("bySupport6", c_byte),
        ("byRes", c_byte * 2)
    ]

class NVRDownloader:
    def __init__(self):
        self.sdk = load_sdk()
        self.lUserID = -1
        self.init_sdk()
    
    def init_sdk(self):
        """初始化SDK"""
        if not self.sdk.NET_DVR_Init():
            error_code = self.sdk.NET_DVR_GetLastError()
            raise Exception(f"SDK初始化失败，错误码: {error_code}")
        
        # 设置连接时间与重连时间
        self.sdk.NET_DVR_SetConnectTime(2000, 1)
        self.sdk.NET_DVR_SetReconnect(10000, True)
    
    def login(self, ip, port, username, password):
        """登录设备（修复版）"""
        login_info = NET_DVR_USER_LOGIN_INFO()
        
        # 正确设置设备地址
        ip_bytes = ip.encode('utf-8')
        if len(ip_bytes) >= len(login_info.sDeviceAddress):
            raise ValueError(f"IP地址过长，最大长度为{len(login_info.sDeviceAddress)-1}字节")
        memset(byref(login_info.sDeviceAddress), 0, len(login_info.sDeviceAddress))
        memmove(byref(login_info.sDeviceAddress), ip_bytes, len(ip_bytes))
        
        # 正确设置用户名
        username_bytes = username.encode('utf-8')
        if len(username_bytes) >= len(login_info.sUserName):
            raise ValueError(f"用户名过长，最大长度为{len(login_info.sUserName)-1}字节")
        memset(byref(login_info.sUserName), 0, len(login_info.sUserName))
        memmove(byref(login_info.sUserName), username_bytes, len(username_bytes))
        
        # 正确设置密码
        password_bytes = password.encode('utf-8')
        if len(password_bytes) >= len(login_info.sPassword):
            raise ValueError(f"密码过长，最大长度为{len(login_info.sPassword)-1}字节")
        memset(byref(login_info.sPassword), 0, len(login_info.sPassword))
        memmove(byref(login_info.sPassword), password_bytes, len(password_bytes))
        
        # 设置端口和登录方式
        login_info.wPort = port
        login_info.bUseAsynLogin = False
        
        device_info = NET_DVR_DEVICEINFO_V40()
        
        self.lUserID = self.sdk.NET_DVR_Login_V40(byref(login_info), byref(device_info))
        if self.lUserID < 0:
            error_code = self.sdk.NET_DVR_GetLastError()
            raise Exception(f"登录失败，错误码: {error_code}")
        
        print(f"登录成功，用户ID: {self.lUserID}")
        return self.lUserID
    
    def datetime_to_net_time(self, dt):
        """将datetime转换为NET_DVR_TIME结构体"""
        net_time = NET_DVR_TIME()
        net_time.dwYear = dt.year
        net_time.dwMonth = dt.month
        net_time.dwDay = dt.day
        net_time.dwHour = dt.hour
        net_time.dwMinute = dt.minute
        net_time.dwSecond = dt.second
        return net_time
    
    def download_by_time(self, channel, start_time, end_time, save_path, stream_type=0):
        """按时间范围下载录像"""
        if self.lUserID < 0:
            raise Exception("请先登录设备")
        
        # 构造播放条件
        play_cond = NET_DVR_PLAYCOND()
        play_cond.dwChannel = channel
        play_cond.struStartTime = self.datetime_to_net_time(start_time)
        play_cond.struStopTime = self.datetime_to_net_time(end_time)
        play_cond.byStreamType = stream_type
        play_cond.byLinkMode = 0  # TCP方式
        
        # 开始下载
        download_handle = self.sdk.NET_DVR_GetFileByTime_V40(
            self.lUserID, 
            save_path.encode('utf-8'), 
            byref(play_cond)
        )
        
        if download_handle < 0:
            error_code = self.sdk.NET_DVR_GetLastError()
            error_msg = f"开始下载失败，错误码: {error_code}"
            raise Exception(error_msg)
        
        # 控制下载开始
        if not self.sdk.NET_DVR_PlayBackControl_V40(download_handle, 0, 0, None):
            error_code = self.sdk.NET_DVR_GetLastError()
            self.sdk.NET_DVR_StopGetFile(download_handle)
            raise Exception(f"控制下载失败，错误码: {error_code}")
        
        print(f"开始下载，保存路径: {save_path}")
        return download_handle
    
    def stop_download(self, download_handle):
        """停止下载"""
        if download_handle >= 0:
            self.sdk.NET_DVR_StopGetFile(download_handle)
            print("下载已停止")
    
    def logout(self):
        """注销登录"""
        if self.lUserID >= 0:
            self.sdk.NET_DVR_Logout(self.lUserID)
            self.lUserID = -1
            print("已注销登录")
    
    def cleanup(self):
        """释放SDK资源"""
        self.sdk.NET_DVR_Cleanup()
        print("SDK资源已释放")

def main():
    # 配置参数
    nvr_ip = "192.168.10.100"    # NVR设备IP地址
    nvr_port = 8000             # 端口号，通常为8000
    username = "admin"          # 登录用户名
    password = "aa147258"       # 登录密码
    channel = 117                # 通道号
    save_path = "recording.mp4" # 保存路径
    
    # 时间范围
    start_time = datetime(2025, 7, 2, 10, 0, 0)
    end_time = datetime(2025, 7, 2, 10, 5, 0)
    
    downloader = NVRDownloader()
    
    try:
        downloader.login(nvr_ip, nvr_port, username, password)
        download_handle = downloader.download_by_time(
            channel, start_time, end_time, save_path, stream_type=0
        )
        
        print("下载中... (按Ctrl+C停止)")
        try:
            while True:
                time.sleep(1)
        except KeyboardInterrupt:
            print("\n用户中断下载")
            downloader.stop_download(download_handle)
        
        print(f"视频下载完成，保存至: {save_path}")
        
    except Exception as e:
        print(f"发生错误: {e}")
    finally:
        downloader.logout()
        downloader.cleanup()

if __name__ == "__main__":
    import time
    main()